using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.IO;
using System.Xml;
using System.Windows.Media.Imaging;
using System.Runtime.Serialization;


namespace ContractDocumentation
{
    class Visualizer
    {
        internal const double HorizontalGap = 16;
        internal const double VerticalGap = 16;

        static XNamespace wsdl = "http://schemas.xmlsoap.org/wsdl/";
        static XNamespace xs = "http://www.w3.org/2001/XMLSchema";
        static XNamespace xhtml = "http://www.w3.org/1999/xhtml";

        internal static IEnumerable<SerializedFile> GenerateImages(SerializedFile[] contractFiles, GeneratorSettings generatorSettings)
        {
            if (generatorSettings == null) generatorSettings = new GeneratorSettings();
            double factor = 1;
            if (generatorSettings.ScaleFactor != null) factor = generatorSettings.ScaleFactor.Value;

            List<SerializedFile> files = new List<SerializedFile>();

            XElement htmlElement = new XElement(xhtml + "html",
                new XElement(xhtml + "head",
                    new XElement(xhtml + "title", "Contract documentation"),
                    new XElement(xhtml + "style",
                        new XAttribute("type", "text/css"),
                        "img { border-width: 0; }"
                    )
                )
            );
            XElement bodyElement = new XElement(xhtml + "body");
            htmlElement.Add(bodyElement);
            XElement mapsParentElement = new XElement(xhtml + "p");

            List<XElement> wsdlElements = new List<XElement>();
            List<XElement> schemaElements = new List<XElement>();
            foreach (SerializedFile contractFile in contractFiles)
            {
                XElement rootElement = XElement.Load(new StreamReader(new MemoryStream(contractFile.Content)));
                if (rootElement.Name == wsdl + "definitions")
                {
                    wsdlElements.Add(rootElement);
                    var q1 = from typesElement in rootElement.Elements(wsdl + "types")
                             from schemaElement in typesElement.Elements(xs + "schema")
                             select schemaElement;
                    foreach (var schemaElement in q1)
                    {
                        schemaElements.Add(schemaElement);
                    }
                }
                else if (rootElement.Name == xs + "schema")
                {
                    schemaElements.Add(rootElement);
                }
                else
                {
                    throw new Exception(string.Format("Unknown root element {0} in {1}", rootElement.Name, contractFile.Name));
                }
            }
            Context context = new Context()
            {
                Wsdls = wsdlElements,
                Schemas = schemaElements,
                ResourceDictionary = (ResourceDictionary)Application.LoadComponent(new Uri("Dictionary.xaml", UriKind.Relative)),
                VisualizerMode = VisualizerMode.Normal,
            };
            var qct = from schemaElement in schemaElements
                     from element in schemaElement.Elements()
                     where element.Name == xs + "complexType"
                     select element;
            foreach (XElement element in qct)
            {
                //element.
            }

            StackPanel allStackPanel = new StackPanel();
            var qw = from definitionElement in wsdlElements
                     from element in definitionElement.Elements(wsdl + "portType")
                     select element;
            var qs = from schemaElement in schemaElements
                     from element in schemaElement.Elements()
                     where element.Name == xs + "element" || element.Name == xs + "complexType"
                     select element;
            foreach (var element in qw.Concat(qs))
            {
                string shortName = (string)element.Attribute("name");
                string id = String.Format("{0}_{1}", element.Name.LocalName, shortName);
                Console.WriteLine(id + "...");
                FrameworkElement frameworkElement;
                if (element.Name == wsdl + "portType")
                {
                    XmlClass xmlClass = PortTypeHelper.ExtractPortType(context, element);
                    frameworkElement = Visualizer.VisualizeClass(context, xmlClass);
                    files.Add(new SerializedFile()
                    {
                        Name = id + ".class.xml",
                        Content = ExportClass(xmlClass),
                    });
                }
                else if (element.Name == xs + "element")
                {
                    frameworkElement = Visualizer.VisualizeGlobalElement(context, element);
                }
                else
                {
                    frameworkElement = Visualizer.VisualizeGlobalComplexType(context, element);
                }
                allStackPanel.Children.Add(frameworkElement);
                frameworkElement.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
                frameworkElement.Arrange(new Rect(frameworkElement.DesiredSize));

                bodyElement.Add(new XElement(xhtml + "h1",
                    new XElement(xhtml + "a",
                        new XAttribute("id", id),
                        shortName
                    )
                ));
                bodyElement.Add(new XElement(xhtml + "p",
                    new XElement(xhtml + "img",
                        new XAttribute("src", id + ".png"),
                        new XAttribute("alt", shortName),
                        new XAttribute("usemap", "#" + id + "_map")
                    )
                ));
                VisualizerTag tag = (VisualizerTag)frameworkElement.Tag;
                if (tag != null && tag.Links != null)
                {
                    mapsParentElement.Add(
                        from link in tag.Links
                        select new XElement(xhtml + "map",
                                    new XAttribute("id", id + "_map"),
                                    new XElement(xhtml + "area",
                                        new XAttribute("coords", (int)link.Rect.Left + "," + (int)link.Rect.Top + "," + (int)link.Rect.Right + "," + (int)link.Rect.Bottom),
                                        new XAttribute("alt", link.Id),
                                        new XAttribute("href", "#" + link.Id)
                                    )
                        )
                    );
                }

                files.Add(new SerializedFile()
                {
                    Name = id + ".png",
                    Content = RenderElementToPng(frameworkElement, factor),
                });
            }

            string allPngFileName = "all.png";
            Console.WriteLine(allPngFileName + "...");
            allStackPanel.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
            allStackPanel.Arrange(new Rect(allStackPanel.DesiredSize));
            files.Add(new SerializedFile()
            {
                Name = allPngFileName,
                Content = RenderElementToPng(allStackPanel, factor),
            });

            //string mapFileName = Path.GetFileNameWithoutExtension(schema.Name) + ".map.xml";
            //Console.WriteLine(mapFileName + "...");
            //var namespaceMapping = new Dictionary<XNamespace, XNamespace>();
            //namespaceMapping["http://www.w3.org/1999/xhtml"] = "";
            //XElement mapsParentElementNoNs = new XElement(mapsParentElement);
            //SchemaUtils.ChangeNamespace(mapsParentElementNoNs, namespaceMapping);
            //MemoryStream mapStream = new MemoryStream();
            //XmlWriter xmlWriter = XmlWriter.Create(mapStream, new XmlWriterSettings() {
            //    Indent = true,
            //    OmitXmlDeclaration = true,
            //    Encoding = new UTF8Encoding(false),
            //});
            //mapsParentElementNoNs.WriteTo(xmlWriter);
            //xmlWriter.Close();
            //files.Add(new SerializedFile()
            //{
            //    Name = mapFileName,
            //    Content = mapStream.ToArray(),
            //});

            string htmlFileName = "all.html";
            Console.WriteLine(htmlFileName + "...");
            MemoryStream htmlStream = new MemoryStream();
            bodyElement.Add(mapsParentElement);
            htmlElement.Save(new StreamWriter(htmlStream));
            files.Add(new SerializedFile()
            {
                Name = htmlFileName,
                Content = htmlStream.ToArray(),
            });

            return files;
        }

        static byte[] RenderElementToPng(FrameworkElement frameworkElement, double factor)
        {
            MemoryStream stream = new MemoryStream();
            RenderTargetBitmap bitmap = new RenderTargetBitmap((int)(frameworkElement.ActualWidth * factor), (int)(frameworkElement.ActualHeight * factor),
                96 * factor, 96 * factor, System.Windows.Media.PixelFormats.Pbgra32);
            bitmap.Render(frameworkElement);
            BitmapEncoder encoder = new PngBitmapEncoder();
            encoder.Frames.Add(BitmapFrame.Create(bitmap));
            encoder.Save(stream);
            stream.Close();
            return stream.ToArray();
        }

        static FrameworkElement VisualizeClass(Context context, XmlClass xmlClass)
        {
            return Wrap(ClassVisualizer.VisualizeClass(context, xmlClass));
        }

        static FrameworkElement VisualizeGlobalElement(Context context, XElement elementElement)
        {
            return Wrap(ElementVisualizer.VisualizeElement(context, elementElement));
        }

        static FrameworkElement VisualizeGlobalComplexType(Context context, XElement complexTypeElement)
        {
            return Wrap(ComplexTypeVisualizer.VisualizeComplexType(context, complexTypeElement));
        }

        static FrameworkElement Wrap(IEnumerable<FrameworkElement> frameworkElements)
        {
            StackPanel stackPanel1 = new StackPanel()
            {
                Background = Brushes.White,
            };
            StackPanel stackPanel2 = new StackPanel()
            {
                Orientation = Orientation.Horizontal,
                Margin = new Thickness(16),
            };
            stackPanel1.Children.Add(stackPanel2);
            FrameworkElement frameworkElement = frameworkElements.Single();
            stackPanel2.Children.Add(frameworkElement);
            VisualizerTag tag = (VisualizerTag)frameworkElement.Tag;
            if (tag != null && tag.Links != null)
            {
                stackPanel1.Tag = new VisualizerTag()
                {
                    Links = UIUtils.LinksOffset(tag.Links, new Vector(16, 16)),
                };
            }
            return stackPanel1;
        }

        static byte[] ExportClass(XmlClass xmlClass)
        {
            DataContractSerializer serializer = new DataContractSerializer(typeof(XmlClass), null, int.MaxValue, false, false, new XNameSurrogate());
            MemoryStream stream = new MemoryStream();
            XmlWriter writer = XmlWriter.Create(stream, new XmlWriterSettings()
            {
                Indent = true,
            });
            serializer.WriteObject(writer, xmlClass);
            writer.Close();
            return stream.ToArray();
        }

    }
}
